package com.livestocksolutions.util;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.GridLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

/**
 * Create a table to store rows of related contents in.
 * @author Adam Parr
 * 
 * @.modified 8 February 2013
 */
public class Table extends JPanel {
	private static final long serialVersionUID = 1L;
	
	/**
	 * The margin that separates each cell in the table
	 */
	protected static final int CELL_MARGIN = 5;
	
	/**
	 * The columns titles used in this table.
	 */
	protected ArrayList<ColumnData> columns = new ArrayList<ColumnData>();

	/**
	 * The data rows provided to display in the table.
	 */
	protected ArrayList<RowData> rows = new ArrayList<RowData>();

	/**
	 * The Component to display when there are no rows to show.
	 */
	protected JPanel rowEmpty = new JPanel();
	
	/**
	 * The JPanel where the table will sit (usually in the BorderLayout.NORTH position to
	 * avoid the table stretching to the entire vertical space when available).
	 */
	protected JPanel tableRow = new JPanel();
	
	/**
	 * Constructor.
	 * @param columnNames The value of each column header used, can be a String or Component.
	 * @param title The Title to give this table.
	 * @param showTitle <tt>true</tt> display the title above the Table.
	 *                  <tt>false</tt> do not show the title.
	 */
	public Table(String title, boolean showTitle, String...columnNames){
		Component[] com = new Component[columnNames.length];
		for(int x=0; x<columnNames.length; x++) {
			com[x]=new JLabel(columnNames[x]);
		}
		createTable(title,showTitle,com);
	}
	
	/**
	 * Constructor.
	 * @param columnNames The value of each column header used, can be a String or Component.
	 * @param title The Title to give this table.
	 * @param showTitle <tt>true</tt> display the title above the Table.
	 *                  <tt>false</tt> do not show the title.
	 */
	public Table(String title, boolean showTitle, Component...columnNames){
		createTable(title,showTitle,columnNames);
	}
	
	/**
	 * Constructor.
	 * @param columnNames The value of each column header used, can be a String or Component.
	 */
	public Table(Component...columnNames){
		createTable(null,false, columnNames);
	}
	
	/**
	 * Constructor.
	 * @param columnNames The value of each column header used, can be a String or Component.
	 */
	public Table(String...columnNames){
		Component[] com = new Component[columnNames.length];
		for(int x=0; x<columnNames.length; x++) {
			com[x]=new JLabel(columnNames[x]);
		}
		createTable(null,false,com);
	}
	
	/**
	 * Create a table with the given number of columns, and the given title.
	 * @param columnNames The value of each column header used, can be a String or Component.
	 * @param title The Title to give this table.
	 * @param showTitle <tt>true</tt> display the title above the Table.
	 *                  <tt>false</tt> do not show the title.
	 */
	protected void createTable(String title, boolean showTitle,Component...columnNames){
		setLayout(new BorderLayout(0,0));
		JPanel base = new JPanel();
		base.setLayout(new BoxLayout(base,BoxLayout.X_AXIS));
		for(Component o : columnNames){
			ColumnData cd = new ColumnData(o);
			columns.add(cd);
			base.add(cd.get());
		}
		tableRow.setLayout(new BorderLayout(0,0));
		if(title!=null && showTitle){
			JLabel lblTitle = new JLabel("<html><body><h2>"+title+"</h2></body></html>");
			JPanel titlePanel = new JPanel();
			titlePanel.add(lblTitle);
			add(titlePanel,BorderLayout.NORTH);
		}
		tableRow.add(base,BorderLayout.NORTH);
		tableRow.add(rowEmpty,BorderLayout.CENTER);
		add(tableRow,BorderLayout.CENTER);
	}
	
	/**
	 * Set the message to display when the table has no rows.
	 * @param message The String message to show.
	 */
	public void setEmptyMessage(String message){
		setEmptyMessage(new JLabel(message));
	}
	
	/**
	 * Set a Component to display when there are no rows to show.
	 * @param message The Component message to show.
	 */
	public void setEmptyMessage(Component message){
		rowEmpty.add(message);
		if(rowEmpty.getParent()!=null){
			tableRow.remove(rowEmpty);
			tableRow.add(rowEmpty,BorderLayout.CENTER);
		}
	}
	
	/**
	 * Use background colours provided through the {@link #updateColours(Color, Color, Color, Color, Color, Color, Color, Color)}
	 * or {@link #updateColours(String, String, String, String, String, String, String, String)}.
	 * @param isOpaque <tt>true</tt> Opaque background, <tt>false</tt> Background is transparent.
	 */
	public void setOpaque(boolean isOpaque){
		try{
			for(ColumnData c: columns){
				c.setOpaque(isOpaque);
			}
			for(RowData r:rows){
				r.setOpaque(isOpaque);
			}
		}catch(Exception e){}
	}
	
	/**
	 * See if the background of the table cells is opaque or transparent.
	 * @return <tt>true</tt> when background is opaque, <tt>false</tt> when background is transparent.
	 */
	public boolean isOpaque(){
		return columns.get(0).isOpaque();
	}
	
	/**
	 * Set the row as selected or not selected.
	 * @param rowNumber The row to set as selected.
	 * @param isSelected <tt>true</tt> Set the row to selected, <tt>false</tt> deselect row.
	 */
	public void setSelected(int rowNumber, boolean isSelected){
		rows.get(rowNumber).setSelected(isSelected);
	}
	
	/**
	 * Set the last row as selected or not selected.
	 * @param isSelected <tt>true</tt> Set the row to selected, <tt>false</tt> deselect row.
	 */
	public void setSelected(boolean isSelected){
		rows.get(rows.size()-1).setSelected(isSelected);
	}
	
	/**
	 * Add a row of values to this table at the end.
	 * @param values The values to add, in the same order as the header titles were given in.
	 */
	public void addRow(Component...values){
		addRow(rows.size(),values);
	}
	
	/**
	 * Add a row of values to this table at the given index.
	 * @param index The position to insert this row into.
	 * @param values The values to add, in the same order as the header titles were given in.
	 */
	public void addRow(int index, Component...values){
		RowData r = new RowData(columns.size(),index,values);
		if(!isOpaque()){
			r.setOpaque(false);
		}
		for(int x=0; x<columns.size(); x++){
			boolean iv=columns.get(x).isVisible();
			if(!iv){
				r.updateCells(x,iv);
			}
		}
		rows.add(index,r);
		if(rows.size()>0&&rowEmpty.getParent()!=null){
			tableRow.remove(rowEmpty);
		}
		for(int j=0; j<columns.size(); j++){
			columns.get(j).add(r.get(j),index);
		}
		if(index!=rows.size()-1){
			for(int i=index+1; i<rows.size(); i++){
				rows.get(i).setIndex(i);
			}
		}
	}
	
	/**
	 * Get the Components of the given Row in column order.
	 * @param rowID The ID of the row to get.
	 * @return A list of components in each cell. If the value of a
	 * given cell was a String, it will be enclosed within a {@link javax.swing.JLabel}.
	 * @see #getRowData(int)
	 */
	public Component[] getRowData(String rowID){
		return getRowData(getRowIndex(rowID));
	}
	
	/**
	 * Get the Components of the given Row in column order.
	 * @param index The position of the Row in the Table to get.
	 * @return A list of components in each cell. If the value of a
	 * given cell was a String, it will be enclosed within a {@link javax.swing.JLabel}.
	 */
	public Component[] getRowData(int index){
		return rows.get(index).getData();
	}
	
	/**
	 * Removes the row of values from this table at the end.
	 */
	public void removeRow(){
		removeRow(rows.size());
	}
	
	/**
	 * Removes the row in the table at the given index position.
	 * @param row The row to remove.
	 */
	@Override
	public void remove(int row) {
		removeRow(row);
	}
	
	/**
	 * Remove the row with given ID from the table.
	 * @param rowID The unique ID of the row to remove.
	 */
	public void remove(String rowID) {
			removeRow(getRowIndex(rowID));
	}
	
	/**
	 * Remove all rows in the Table.
	 */
	@Override
	public void removeAll() {
		while(rows.size()>0) {
			removeRow(0);
		}
	}
	
	/**
	 * Set the border around the table to the given Border.
	 * @param border Border to display.
	 */
	public void setTableBorder(Border border) {
		((JPanel)tableRow.getComponent(0)).setBorder(border);
	}
	
	/**
	 * Removes the row of values from this table at the given index.
	 * @param index The position to remove the row from.
	 */
	public void removeRow(int index){
		if(index>=0&&index<rows.size()){
			rows.remove(index);
			for(int i=0; i<columns.size(); i++){
				columns.get(i).remove(index);
			}
			if(index!=rows.size()){
				for(int i=index; i<rows.size(); i++){
					rows.get(i).setIndex(i);
				}
			}
			
			if(rows.size()==0){
				tableRow.add(rowEmpty,BorderLayout.CENTER);
			}
			validate();
		}
	}
	
	/**
	 * Show the header row.
	 * @param isVisible <tt>true</tt> (default) show the header row, <tt>false</tt> do not show.
	 */
	public void showHeaderVisible(boolean isVisible) {
		for(ColumnData c : columns) {
			c.setHeaderVisible(isVisible);
		}
	}
	
	/**
	 * Show or hide a column in the table.
	 * @param columnIndex The Column to hide as indicated from left to right where the first column is at position 0.
	 * @param isVisible <tt>true</tt> Show the column (default value), <tt>false</tt> hide column.
	 */
	public void setColumnVisible(int columnIndex, boolean isVisible){
		if(columnIndex<columns.size()){
			columns.get(columnIndex).setVisible(isVisible);
			for(RowData r:rows){
				r.updateCells(columnIndex,isVisible);
			}
		}
	}
	
	/**
	 * See if the column is visible or not.
	 * @param columnIndex The Column to hide as indicated from left to right where the first column is at position 0.
	 * @return <tt>true</tt> if the column is visible, <tt>false</tt> otherwise.
	 */
	public boolean isColumnVisible(int columnIndex){
		if(columnIndex<columns.size())
			return columns.get(columnIndex).isVisible();
		return false;
	}
	
	/**
	 * Set a column as the ordered column, but which the entered data is already sorted by that column.
	 * @param headerIndex The column that is sorted.
	 * @param highToLow The direction the arrow points, <tt>true</tt> uses descending arrow,
	 *                   while <tt>false</tt> uses ascending arrow.
	 */
	public void setHeaderOrderedBy(int headerIndex, boolean highToLow){
		columns.get(headerIndex).setHeaderOrderedBy(highToLow);
	}
	
	/**
	 * Un-set a column as the ordered column.
	 * @param headerIndex The column to remove the sorted arrow from.
	 */
	public void removeHeaderOrderedBy(int headerIndex){
		columns.get(headerIndex).removeHeaderOrderedBy();
	}
	
	/**
	 * Set if this column can be clickable.
	 * @param headerIndex The column header to set clickable or not.
	 * @param clickable <tt>true</tt> this column header will react to mouse events.
	 */
	public void setHeaderClickable(int headerIndex, boolean clickable){
		columns.get(headerIndex).setClickable(clickable);
	}
	
	/**
	 * Set the border around the row at the end of the list to the given Border.
	 * @param border Border to display.
	 */
	public void setRowBorder(Border border) {
		setRowBorder(rows.size()-1,border);
	}
	
	/**
	 * Set the border around the row at the given index to the given Border.
	 * @param index The Row to apply the border to.
	 * @param border Border to display.
	 */
	public void setRowBorder(int index,Border border) {
		rows.get(index).setBorder(border);
	}
	
	/**
	 * See if this column is clickable.
	 * @param headerIndex The column header to check if clickable or not.
	 * @return <tt>true</tt> this row will react to mouse events.
	 */
	public boolean isHeaderClickable(int headerIndex){
		return columns.get(headerIndex).isClickable();
	}
	
	/**
	 * Add a mouse listener event to the column at the given index.
	 * @param index The column to add the MouseListener to.
	 * @param ml The MouseListener to add to the column.
	 */
	public void addHeaderMouseListener(int index, MouseListener ml){
		columns.get(index).addMouseListener(ml);
	}

	/**
	 * Remove a mouse listener event from the column header at the given index.
	 * @param index The column to remove the MouseListener from.
	 * @param ml The MouseListener to remove from the column.
	 */
	public void removeHeaderMouseListener(int index, MouseListener ml){
		columns.get(index).removeMouseListener(ml);
	}
	
	/**
	 * Add a mouse listener event from the column header at the given index to the given Component.
	 * @param index The column to add the MouseListener from to the given Component.
	 * @param component The MouseListener to add the default MouseListener used in this column.
	 */
	public void addDefaultHeaderMouseListenerTo(int index, Component component){
		columns.get(index).addDefaultListenerTo(component);
	}
	
	/**
	 * Add a mouse listener event to the row at the end of the list.
	 * @param ml The MouseListener to add to the row.
	 */
	@Override
	public synchronized void addMouseListener(MouseListener ml){
		addMouseListener(rows.size()-1, ml);
	}
	
	/**
	 * Add a mouse listener event to the row at the given index.
	 * @param index The row to add the MouseListener to.
	 * @param ml The MouseListener to add to the row.
	 */
	public void addMouseListener(int index, MouseListener ml){
		rows.get(index).addMouseListener(ml);
	}
	
	/**
	 * Remove a mouse listener event from the row at the end of the list.
	 * @param ml The MouseListener to remove from the row.
	 */
	@Override
	public synchronized void removeMouseListener(MouseListener ml){
		removeMouseListener(rows.size()-1, ml);
	}
	
	/**
	 * Remove a mouse listener event from the row at the given index.
	 * @param index The row to remove the MouseListener from.
	 * @param ml The MouseListener to remove from the row.
	 */
	public void removeMouseListener(int index, MouseListener ml){
		rows.get(index).removeMouseListener(ml);
	}
	
	/**
	 * Add a mouse listener event from the row at the end of the list to the given Component.
	 * @param component The MouseListener to add the default MouseListener used in this row.
	 */
	public void addDefaultMouseListenerTo(Component component){
		addDefaultMouseListenerTo(rows.size()-1, component);
	}
	
	/**
	 * Add a mouse listener event from the row at the given index to the given Component.
	 * @param index The row to add the MouseListener from to the given Component.
	 * @param component The MouseListener to add the default MouseListener used in this row.
	 */
	public void addDefaultMouseListenerTo(int index, Component component){
		rows.get(index).addDefaultListenerTo(component);
	}
	
	/**
	 * Set if the last row can be clickable.
	 * @param clickable <tt>true</tt> this row will react to mouse events.
	 */
	public void setRowClickable(boolean clickable){
		setRowClickable(rows.size()-1,clickable);
	}
	
	/**
	 * Set if this row can be clickable.
	 * @param index The row to set clickable or not.
	 * @param clickable <tt>true</tt> this row will react to mouse events.
	 */
	public void setRowClickable(int index, boolean clickable){
		if(rows.size()>index&&index>=0){
			rows.get(index).setClickable(clickable);
		}
	}
	
	/**
	 * See if the last row is clickable.
	 * @return <tt>true</tt> this row will react to mouse events.
	 */
	public boolean isRowClickable(){
		return isRowClickable(rows.size()-1);
	}
	
	/**
	 * See if this row is clickable.
	 * @param index The row to check if clickable or not.
	 * @return <tt>true</tt> this row will react to mouse events.
	 */
	public boolean isRowClickable(int index){
		return rows.get(index).isClickable();
	}
	
	/**
	 * Get the number of rows in this table.
	 * @return An integer greater than or equal to 0, that represent the number of rows in the table.
	 */
	public int getNumRows() {
		return rows.size();
	}
	/**
	 * Set the colours used as backgrounds in the tables.
	 * @param selected The background colour to show when a column header is clicked on,
	 *                 or table is ordered by that column.
	 * @param normal The background colour to show when a column is in its normal state, as a Hexadecimal String.
	 * @param hover The background colour to show when the mouse hovers over a row, as a Hexadecimal String.
	 * @param click The background colour to show when the mouse clicks on a row, as a Hexadecimal String.
	 * @param oddOdd The background colour to set an odd indexed cell in an odd indexed row, as a Hexadecimal String.
	 * @param oddEven The background colour to set an even indexed cell in an odd indexed row, as a Hexadecimal String.
	 * @param evenOdd The background colour to set an odd indexed cell in an even indexed row, as a Hexadecimal String.
	 * @param evenEven The background colour to set an even indexed cell in an even indexed row, as a Hexadecimal String.
	 */
	public static void updateColours(String selected, String normal, String hover, String click, String oddOdd, String oddEven, String evenOdd, String evenEven){
		updateColours(Color.decode(selected), Color.decode(normal),Color.decode(hover), Color.decode(click), Color.decode(oddOdd),Color.decode(oddEven), Color.decode(evenOdd), Color.decode(evenEven));
	}
	
	/**
	 * Set the colours used as backgrounds in the tables.
	 * @param selected The background colour to show when a table is ordered by that column.
	 * @param normal The background colour to show when a column is in its normal state.
	 * @param hover The background colour to show when the mouse hovers over a row.
	 * @param click The background colour to show when the mouse clicks on a row.
	 * @param oddOdd The background colour to set an odd indexed cell in an odd indexed row.
	 * @param oddEven The background colour to set an even indexed cell in an odd indexed row.
	 * @param evenOdd The background colour to set an odd indexed cell in an even indexed row.
	 * @param evenEven The background colour to set an even indexed cell in an even indexed row.
	 */
	public static void updateColours(Color selected, Color normal, Color hover, Color click, Color oddOdd, Color oddEven, Color evenOdd, Color evenEven){
		ColumnData.updateColours(selected, normal);
		RowData.updateColours(hover, click, oddOdd, oddEven, evenOdd, evenEven);
	}
	
	/**
	 * Get the id of the row at the given position.
	 * @param row The row to get the unique ID for.
	 * @return A String providing a reference to the row at the given position row in the table.
	 */
	public String getRowID(int row) {
		return Integer.toHexString(rows.get(row).hashCode());
	}
	
	/**
	 * Get the position of the row in the table.
	 * @param rowID String providing a reference to the row at the given position row in the table.
	 * @return The index of the row at the current time.
	 */
	public int getRowIndex(String rowID){
		for(RowData r:rows){
			if(Integer.toHexString(r.hashCode()).equals(rowID)){
				return r.getIndex();
			}
		}
		return -1;
	}
	
	/**
	 * A class that holds the detail of a single column, including the header.
	 * @author Adam Parr
	 * 
	 * @.modified 5 February 2013
	 */
	protected static class ColumnData extends RowData{
		/**
		 * The colour to set when the cell header has been.<br>
		 * {@link #updateColours(Color, Color)} must be called first to avoid cells having the default JPanel colour.
		 */
		protected static Color headerSelected;
		/**
		 * The mouse clicked colour to set the row to.<br>
		 * {@link #updateColours(Color, Color)} must be called first to avoid cells having the default JPanel colour.
		 */
		protected static Color headerNormal;
		
		/**
		 * The Column of this table.
		 */
		protected JPanel cells;
		
		/**
		 * Header of this Column.
		 */
		protected JPanel header;
		
		/**
		 * Create a column with the given data.
		 * @param cellData The Data to insert into each row cell.
		 */
		public ColumnData(Component cellData) {
			super(1, 1, cellData);
			
			cells = new JPanel();
			cells.setLayout(new GridLayout(0,1));
			header = new JPanel();
			header.setBorder(new EmptyBorder(CELL_MARGIN,CELL_MARGIN,CELL_MARGIN,CELL_MARGIN));
			header.setLayout(new BorderLayout(0,0));
			header.setBackground(headerNormal);
			header.add(cellData != null?(Component)cellData:new JLabel(),BorderLayout.CENTER);
			header.addMouseListener(ml);
			header.addMouseListener(mlClicked);
			cells.add(header);
		}

		/**
		 * Set the column header as visible or invisible.
		 * @param isVisible <tt>true</tt> Set the column header as visible. <tt>false</tt> column header is invisible.
		 */
		public void setHeaderVisible(boolean isVisible) {
			if(isVisible) {
				cells.add(header,0);
				cells.validate();
			}else {
				cells.remove(header);
			}
		}
		
		/**
		 * Set the column as being visible or not.
		 * @param isVisible <tt>true</tt> if the column is visible, <tt>false</tt> if it is not.
		 */
		public void setVisible(boolean isVisible) {
			cells.setVisible(isVisible);
		}
		
		/**
		 * Check if the column is visible or not.
		 * @return <tt>true</tt> if the column is visible, <tt>false</tt> if it is not.
		 */
		public boolean isVisible(){
			return cells.isVisible();
		}
		
		/**
		 * Set the column as the ordered column, but which the entered data is already sorted by this column.
		 * @param highToLow The direction the arrow points, <tt>true</tt> uses descending arrow,
		 *                   while <tt>false</tt> uses ascending arrow.
		 */
		public void setHeaderOrderedBy(boolean highToLow){
			JLabel orderSeq = new JLabel(UIManager.getIcon("Table."+(!highToLow?"ascending":"descending")+"SortIcon"));
			((JPanel)cells.getComponent(0)).add(orderSeq,BorderLayout.EAST);
			cells.getComponent(0).setBackground(headerSelected);
		}
		
		/**
		 * Un-set a column as the ordered column.
		 */
		public void removeHeaderOrderedBy(){
			if(((JPanel)cells.getComponent(0)).getComponentCount()>1){
				((JPanel)cells.getComponent(0)).remove(1);
				cells.getComponent(0).setBackground(headerNormal);
			}
		}
		
		/**
		 * Get the JPanel and the contents for that cell at the position given.
		 * @return the JPanel containing the data for that cell, or null if cell is out of range.
		 */
		public JPanel get(){
			return cells;
		}
		
		/**
		 * Removes all the components from this container. This method also notifies
		 * the layout manager to remove the components from this container's layout
		 * via the removeLayoutComponent method.
		 * 
		 * @see java.awt.Container#removeAll()
		 */
		public void removeAll(){
			for(int x = 1; x<cells.getComponentCount(); x++){
				cells.remove(x);
			}
		}
		
		/**
		 * Removes the component, specified by index, from this container.
		 * This method also notifies the layout manager to remove the component
		 * from this container's layout via the removeLayoutComponent method.
		 * Note: If a component has been removed from a container that had
		 * been displayed, validate() must be called on that container to
		 * reflect changes. If multiple components are being removed, you
		 * can improve efficiency by calling validate() only once, after all
		 * the components have been removed.
		 * @param index the index of the component to be removed
		 * 
		 * @see java.awt.Container#remove(int)
		 */
		public void remove(int index){
			cells.remove(index+(cells.isAncestorOf(header)?1:0));
		}
		
		/**
		 * Gets the number of components in this panel.
		 * @return the number of components in this panel.
		 * 
		 * @see java.awt.Container#getComponentCount()
		 */
		public int getComponentCount(){
			return cells.getComponentCount()-(cells.isAncestorOf(header)?1:0);
		}
		
		/**
		 * Appends the specified component to the end of this container.
		 * This is a convenience method for addImpl(java.awt.Component, java.lang.Object, int).<br>
		 * Note: If a component has been added to a container that has been
		 * displayed, validate must be called on that container to display
		 * the new component. If multiple components are being added,
		 * you can improve efficiency by calling validate only once,
		 * after all the components have been added.
		 * @param component the component to be added
		 * @return the component argument
		 * 
		 * @see java.awt.Container#add(Component)
		 */
		public Component add(Component component){
			return cells.add(component);
		}
		
		/**
		 * Add a cell to the column at the specified position.
		 * @param cell The cell to add to the column.
		 * @param index The position to add the cell to.
		 * @return The cell argument.
		 */
		public Component add(Component cell, int index) {
			return cells.add(cell, index+(cells.isAncestorOf(header)?1:0));
		}
		
		@Override
		public void performAction(int action){
			if(!opaque){
				cells.getComponent(0).setBackground(new Color(0f, 0f, 0f, 1f));
				((JPanel)cells.getComponent(0)).setOpaque(opaque);
				return;
			}
			if(clickable){
				cells.setCursor (Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
				switch(action){
					case ACTION_CLICK:
					case ACTION_RELEASED:
					case ACTION_EXIT:
						cells.getComponent(0).setBackground(((JPanel)cells.getComponent(0)).getComponentCount()>1?headerSelected:headerNormal);
						break;
					case ACTION_PRESSED:
						cells.getComponent(0).setBackground(clickColour);
						break;
					case ACTION_ENTER:
						cells.getComponent(0).setBackground(highlight);
						break;
				}
			}else {
				cells.setCursor (Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
		}
		
		/**
		 * Set the background colours which are shown when the respective action occurs.
		 * @param selected The background colour to show when the table is ordered by this column.
		 * @param normal The background colour to show when the column is in its normal state.
		 */
		public static void updateColours(Color selected, Color normal){
			headerSelected = selected;
			headerNormal = normal;
		}
	}
	
	/**
	 * A class that holds all row specific attributes, such as values for each column,
	 * mouse event listeners for highlighting of a cell when the mouse hovers over, and clicks
	 * @author Adam Parr
	 * 
	 * @.modified 5 February 2013
	 */
	protected static class RowData{
		/**
		 * Used to identify when a mouse click action occurs on this row.
		 */
		public static final int ACTION_CLICK = 0;
		/**
		 * Used to identify when a mouse pressed action occurs on this row.
		 */
		public static final int ACTION_PRESSED = 1;
		/**
		 * Used to identify when a mouse released action occurs on this row.
		 */
		public static final int ACTION_RELEASED= 2;
		/**
		 * Used to identify when a mouse has entered this row.
		 */
		public static final int ACTION_ENTER= 3;
		/**
		 * Used to identify when a mouse has exited this row.
		 */
		public static final int ACTION_EXIT= 4;
		
		/**
		 * The Mouse over colour to set the row to.<br>
		 * {@link #updateColours(Color, Color, Color, Color, Color, Color)} must be called first to avoid cells having the default JPanel colour.
		 */
		protected static Color highlight;
		/**
		 * The mouse clicked colour to set the row to.<br>
		 * {@link #updateColours(Color, Color, Color, Color, Color, Color)} must be called first to avoid cells having the default JPanel colour.
		 */
		protected static Color clickColour;
		
		/**
		 * Colour used for an Odd Rows Odd numbered cells.
		 */
		protected static Color selectOddOdd;
		/**
		 * Colour used for an Odd Rows Even numbered cells.
		 */
		protected static Color selectOddEven;
		/**
		 * Colour used for an Even Rows Odd numbered cells.
		 */
		protected static Color selectEvenOdd;
		/**
		 * Colour used for an Even Rows Even numbered cells.
		 */
		protected static Color selectEvenEven;

		/**
		 * The position of the Row. Determines the colour of the row.
		 */
		protected int rowIndex;
		
		/**
		 * Stores which columns are visible and which are not.<br>
		 * <tt>true</tt> The column is visible, <tt>false</tt> column is not visible.
		 */
		protected boolean[] columnVisibility;
		
		/**
		 * The event listener that listens for mouse events before performing an action
		 */
		protected MouseListener ml =new MouseListener(){
			@Override public void mouseClicked(MouseEvent e) {performAction(ACTION_CLICK);}
			@Override public void mousePressed(MouseEvent e) {performAction(ACTION_PRESSED); pressed(e);}
			@Override public void mouseReleased(MouseEvent e) {performAction(ACTION_RELEASED); released(e);}
			@Override public void mouseEntered(MouseEvent e) {performAction(ACTION_ENTER); entered(e);}
			@Override public void mouseExited(MouseEvent e) {performAction(ACTION_EXIT); exited(e);}
		};
		
		/**
		 * The event listener that listens for the mouseClicked event
		 */
		protected MouseListener mlClicked =new MouseListener(){
			@Override public void mouseClicked(MouseEvent e) {click(e);}
			@Override public void mousePressed(MouseEvent e) {}
			@Override public void mouseReleased(MouseEvent e) {}
			@Override public void mouseEntered(MouseEvent e) {}
			@Override public void mouseExited(MouseEvent e) {}
		};
		
		/**
		 * All registered mouse events that are set to occur upon an event happening.
		 */
		protected ArrayList<MouseListener> events = new ArrayList<MouseListener>();
		
		/**
		 * The cells in the column.
		 */
		protected JPanel[] cells;
		
		/**
		 * Determines if this row is clickable, <tt>true</tt> will allow the row to react to mouse events,
		 * <tt>false</tt> will prevent mouse events from occurring.<br>
		 * Note: This does not prevent mouse events from occurring to the underlying JPanel and children,
		 * only MouseEvents added through the {@link #addMouseListener(MouseListener)} method.<br><br>
		 * defaults to <tt>true</tt>
		 */
		protected boolean clickable = true;
		
		/**
		 * Determines if the row is selected.<br>
		 * <tt>true</tt> the row is selected, <tt>false</tt> the row is not selected.
		 */
		protected boolean selected = false;
		
		/**
		 * Determine if Cell colours should be shown or not.<br>
		 * <tt>true</tt> Show background colours. <tt>false</tt> allow transparency.
		 */
		protected boolean opaque = true;
		
		/**
		 * Create a row with the given data, where each object is a different cell.
		 * @param columnCount The number of columns this row has to fill.
		 * @param rowIndex Index number of this row in the table.
		 * @param cellData The Data to insert into each row cell.
		 */
		public RowData(int columnCount, int rowIndex, Component...cellData){
			cells = new JPanel[columnCount];
			columnVisibility = new boolean[columnCount];
			this.rowIndex=rowIndex;
			for(int x = 0; x<columnCount; x++){
				columnVisibility[x]=true;
				JPanel j = new JPanel();
				j.setLayout(new BorderLayout(0,0));
				if(x<cellData.length&&cellData[x]!=null&&cellData[x] instanceof JComponent) {
					((JComponent)cellData[x]).setOpaque(false);
				}
				
				JPanel p = new JPanel();
				p.setBorder(new EmptyBorder(CELL_MARGIN,CELL_MARGIN,CELL_MARGIN,CELL_MARGIN));
				p.setOpaque(false);
				p.setLayout(new BorderLayout(0,0));
				p.add(x<cellData.length&&cellData[x]!=null?cellData[x]:new JLabel(),BorderLayout.CENTER);
				j.add(p);
				j.setBackground(rowIndex%2==0?x%2==0?selectEvenEven:selectEvenOdd:x%2==0?selectOddEven:selectOddOdd);
				j.addMouseListener(ml);
				j.addMouseListener(mlClicked);
				cells[x]=j;
			}
		}
		
		/**
		 * Get the Components of the Row in column order.
		 * @return A list of components in each cell. If the value of a
		 * given cell was a String, it will be enclosed within a {@link javax.swing.JLabel}.
		 */
		public Component[] getData() {
			int length = cells.length;
			Component[] components = new Component[length];
			for(int x=0; x<length;x++){
				components[x]=get(x).getComponent(0);
				components[x]=components[x]!=null&&((JPanel)components[x]).getComponentCount()>0?((JPanel)components[x]).getComponent(0):null;
			}
			return components;
		}

		/**
		 * Update cells if the visibility of a column changes.
		 * @param column The column that has been hidden or shown.
		 * @param isVisible <tt>true</tt> if the column is set to be visible, <tt>false</tt> otherwise.
		 */
		public boolean updateCells(int column, boolean isVisible){
			if(column<columnVisibility.length){
				if(columnVisibility[column]!=isVisible){
					columnVisibility[column]=isVisible;
					int x=0;
					for(int y=0; y<columnVisibility.length;y++){
						cells[y].setBackground(rowIndex%2==0?x%2==0?selectEvenEven:selectEvenOdd:x%2==0?selectOddEven:selectOddOdd);
						x+=columnVisibility[y]?1:0;
					}
					return true;
				}
			}
			return false;
		}
		
		/**
		 * Update the position of this row in the table.
		 * @param rowIndex The new position of this row in the table
		 */
		public void setIndex(int rowIndex){
			this.rowIndex=rowIndex;
			boolean clk = clickable;
			clickable=true;
			performAction(ACTION_EXIT);
			clickable=clk;
		}
		
		/**
		 * Get the index position of this row.
		 * @return An integer value indicating the rows position within the table.
		 */
		public int getIndex(){
			return rowIndex;
		}
		
		/**
		 * Set a border for this Row.
		 * @param border The border to set.
		 */
		public void setBorder(Border border) {
			for(int x=0; x<cells.length; x++) {
				cells[x].setBorder(border);
			}
		}

		/**
		 * Set this Row as being clickable.
		 * @param clickable <tt>true</tt> this row will react to mouse events.
		 */
		public void setClickable(boolean clickable){
			this.clickable=clickable;
		}
		
		/**
		 * Set this row as being selected/highlighted all the time.
		 * @param isSelected <tt>true</tt> the row is selected, <tt>false</tt> the row is not selected.
		 */
		public void setSelected(boolean isSelected){
			selected=isSelected;
			boolean clk = clickable;
			clickable=true;
			performAction(ACTION_EXIT);
			clickable=clk;
		}
		
		/**
		 * See if this row is clickable.
		 * @return <tt>true</tt> this row will react to mouse events.
		 */
		public boolean isClickable(){
			return clickable;
		}
		
		/**
		 * See if the row is selected
		 * @return <tt>true</tt> the row is selected, <tt>false</tt> the row is not selected.
		 */
		public boolean isSelected(){
			return selected;
		}
		
		/**
		 * Get the JPanel and the contents for that cell at the position given.
		 * @param cell The index of the Cell to return.
		 * @return the JPanel containing the data for that cell, or null if cell is out of range.
		 */
		public JPanel get(int cell){
			return cell>=0&&cell<cells.length?(JPanel)cells[cell]:null;
		}
		
		/**
		 * Event that is called when a mouse click is registered as being clicked on the row.
		 * @param ev The event that has occurred.
		 */
		public void click(MouseEvent ev){
			if(clickable){
				for(MouseListener m : events){
					m.mouseClicked(ev);
				}
			}
		}
		
		/**
		 * Event that is called when a mouse pointer is registered as being released, after a press event.
		 * @param ev The event that has occurred.
		 */
		public void released(MouseEvent ev){
			if(clickable){
				for(MouseListener m : events){
					m.mouseReleased(ev);
				}
			}
		}
		
		/**
		 * Event that is called when a mouse pointer is registered as being pressed in the row.
		 * @param ev The event that has occurred.
		 */
		public void pressed(MouseEvent ev){
			if(clickable){
				for(MouseListener m : events){
					m.mousePressed(ev);
				}
			}
		}
		
		/**
		 * Event that is called when a mouse pointer is registered as being has moved out of the row.
		 * @param ev The event that has occurred.
		 */
		public void exited(MouseEvent ev){
			if(clickable){
				for(MouseListener m : events){
					m.mouseExited(ev);
				}
			}
		}
		
		/**
		 * Event that is called when a mouse pointer is registered as being has moved into the row.
		 * @param ev The event that has occurred.
		 */
		public void entered(MouseEvent ev){
			if(clickable){
				for(MouseListener m : events){
					m.mouseEntered(ev);
				}
			}
		}
		
		/**
		 * Set if Cell colours should be shown or not.
		 * @param isOpaque <tt>true</tt> Show background colours. <tt>false</tt> allow transparency.
		 */
		public void setOpaque(boolean isOpaque){
			opaque=isOpaque;
			boolean clk = clickable;
			clickable=true;
			performAction(ACTION_EXIT);
			clickable=clk;
		}
		
		/**
		 * Check to see if this Row is already opaque.
		 * @return  <tt>true</tt> Row is Opaque. <tt>false</tt> Row is transparent.
		 */
		public boolean isOpaque(){
			return opaque;
		}
		
		/**
		 * When a MouseEvent occurs on one of the JPanels, or any Components set to
		 * listen to the default MouseEventListener, this method determines what
		 * should be done to the background colours of JPanels on this row.
		 * @param action The Action that has occurred.
		 */
		public void performAction(int action){
			if(clickable){
				int x=0;
				for(int y = 0; y<cells.length; y++){
					cells[y].setCursor (Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
					switch(action){
						case ACTION_CLICK:
						case ACTION_RELEASED:
						case ACTION_EXIT:
							if(!opaque){
								cells[y].setBackground(new Color(0f, 0f, 0f, 1f));
								cells[y].setOpaque(opaque);
								break;
							}
							if(!selected){
								cells[y].setBackground(rowIndex%2==0?x%2==0?selectEvenEven:selectEvenOdd:x%2==0?selectOddEven:selectOddOdd);
								x+=columnVisibility[y]?1:0;
							}else{
								cells[y].setBackground(highlight);
							}
							break;
						case ACTION_PRESSED:
							cells[y].setBackground(clickColour);
							break;
						case ACTION_ENTER:
							cells[y].setBackground(highlight);
							break;
					}
				}
			}else {
				for(int x = 0; x<cells.length; x++){
					cells[x].setCursor (Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				}
			}
		}
		
		/**
		 * Add the default mouse listener to add to this component.
		 * @param component The component to add the event listener to.
		 */
		public void addDefaultListenerTo(Component component){
			component.addMouseListener(ml);
		}
		
		/**
		 * Add a Mouse Listener event to this row.
		 * @param ml The Mouse Listener to add to this row.
		 */
		public void addMouseListener(MouseListener ml){
			events.add(ml);
		}
		
		/**
		 * Remove a Mouse Listener event from this row.
		 * @param ml The Mouse Listener to remove from this row.
		 */
		public void removeMouseListener(MouseListener ml){
			events.remove(ml);
		}
		
		/**
		 * Set the background colours which are shown when the respective action occurs.
		 * @param hover The background colour to show when the mouse hovers over the row.
		 * @param click The background colour to show when the mouse clicks on the row.
		 * @param oddOdd The background colour to set an odd indexed cell in an odd indexed row.
		 * @param oddEven The background colour to set an even indexed cell in an odd indexed row.
		 * @param evenOdd The background colour to set an odd indexed cell in an even indexed row.
		 * @param evenEven The background colour to set an even indexed cell in an even indexed row.
		 */
		public static void updateColours(Color hover, Color click, Color oddOdd, Color oddEven, Color evenOdd, Color evenEven){
			highlight = hover;
			clickColour = click;
			selectEvenEven = evenEven;
			selectEvenOdd = evenOdd;
			selectOddEven = oddEven;
			selectOddOdd = oddOdd;
		}
	}
}
